
package taskmanager;

import mpiserver.MpiEvent;
import Network.ISlaveServer;
import Network.NetworkAddress;
import Network.NetworkLogic;
import Network.s2sInnerLogic;
import java.rmi.RemoteException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import repository.LongComparable;

/**
 * Планировщик верхнего уровня
 * Доступен только в рамках пакета "taskmanager"
 * @author Aleksey ID=2001
 */
class GlobalTaskManager implements IGlobalTaskManager {
    /** таблица соответствия id сервера -> TaskManager*/
    private ConcurrentHashMap<LongComparable,NetworkAddress> serversTable;
    /** таблица соответствия id задачи -> JTask*/
    private ConcurrentHashMap<LongComparable,JTask> taskTable;
    /** таблица соответствия id задачи -> id сервера*/
    private ConcurrentHashMap<LongComparable,LongComparable> taskServersTable;
    /** локальный планировщик нижнего уровня (свой) */
    private LocalTaskManager localTM;
    /** задачи, не распределенные между серверами из-за ошибки сети */
    private LinkedList<JTask> strayedTasks;
    /** ссылка на объект управления сетью */
    private s2sInnerLogic s2slogic;

    /**
     * конструктор
     * @param localTM локальный планировщик нижнего уровня
     */
    GlobalTaskManager(NetworkLogic netlogic,LocalTaskManager localTM){
        s2slogic=netlogic.getS2S();
        this.localTM=localTM;
        taskTable= new ConcurrentHashMap<LongComparable,JTask>();
        serversTable= new ConcurrentHashMap<LongComparable,NetworkAddress>();
        taskServersTable= new ConcurrentHashMap<LongComparable,LongComparable>();
        strayedTasks=new LinkedList<JTask>();
    }

    /**
     * Определить наименее загруженный сервер.
     * Нагрузка вычисляется по формуле НАГРУЗКА=ЧИСЛО_ЗАДАЧ/МАКС_ЧИСЛО_АКТИВНЫХ.
     * Сервер с максимальным числом активных задач = 0 в рассмотрение не вкоючается.
     * Сервер к которому не удалось подключиться - также игнорируется
     * @return идентификатор сервера
     * @throws java.lang.Exception не удалось сделать выбор
     */
    private long getMostFreeServer() throws Exception{
        long bestServ; //ID лучшего сервера
        float minCharge=0; //нагрузка на сервер, чем меньше, тем лучше
        float tmp;
        ISlaveServer server;
        //проверить локальный планировщик
        minCharge=localTM.getTaskCount()/localTM.getMaxActiveCount();
        bestServ=s2slogic.getGUID();
        //проверить остальные серверы
        for(NetworkAddress address:serversTable.values()){
            try {
                //получить заглушку
                server = s2slogic.getIArbitrator().getSlaveStub(address);
                //проверить нагрузку сервера
                tmp=server.getTaskCount()/server.getMaxActiveCount();
                //игнорировать недопустимые значения
                if(Float.isNaN(tmp) || Float.isInfinite(tmp)) continue;
                //сравнить с текущими значениями
                if(Float.isNaN(minCharge) || Float.isInfinite(minCharge)
                                                            || minCharge>tmp){
                    bestServ=address.getGUID();    //запомнить значения
                    minCharge=tmp;
                }
            } catch (RemoteException ex) {} //ошибка соединения - пропустить
        }
        //проверить на допустимость значений
        if(Float.isNaN(minCharge) || Float.isInfinite(minCharge))
            throw new Exception();
        return bestServ;
    }

    /**
     * Запросить количество задач в планировщике
     * @return число задач в планировщике
     */
    public int getTaskCount(){
        return taskTable.size();
    }
    
    /**
     * Проверить наличие задач в планировщике
     * @return true - здач нет, иначе false
     */
    public boolean isEmpty(){
        return taskTable.isEmpty();
    }

    /**
     * Добавить подчиненный сервер.
     * @param NetworkAddress адрес сервера
     * @return true - сервер добавлен, false - невозможно добавить сервер
     */
    public boolean addServer(NetworkAddress address){
        if(address==null) throw new NullPointerException();
        //проверить наличие сервера в системе
        LongComparable tempID=new LongComparable(address.getGUID());
        if(serversTable.containsKey(tempID)) return false;
        //добавить сервер в таблицу
        serversTable.put(tempID, address);
        return true;
    }

    /**
     * Удалить подчиненный сервер.
     * @param id идентификатор сервера
     * @return true - сервер добавлен, false - сервер с таким id отсутствует
     * или ошибка сети
     */
    public boolean removeServer(long id){
        //проверить наличие сервера в системе
        LongComparable tempID=new LongComparable(id);
        if(serversTable.containsKey(tempID)) return false;
        //удалить сервер из таблицы
        serversTable.remove(tempID);
        //выбрать задачи отключаемого сервера
        Entry<LongComparable,LongComparable> entr;
        Iterator<Entry<LongComparable,LongComparable>> iter =
                taskServersTable.entrySet().iterator();
        //перебрать хэш ID задачи -> ID сервера
        while(iter.hasNext()){
            entr=iter.next();
            //если найден удаляемый сервер
            if(entr.getValue().value==id){
                //
                // ДОПИСАТЬ функцию распредеения задачи на другой сервер
                //
                //добавить задачу к неопределенным
                strayedTasks.push(taskTable.get(entr.getKey()));
                //удалить задачу из хэша ID задачи -> ID сервера
                iter.remove();
            }
        }
        return true;
    }

    /**
     * Проверить наличие сервера
     * @param taskID идентификатор сервера
     * @return true - сервер присутствует, false иначе
     */
    public boolean containsServer(long id){
        return serversTable.containsKey(new LongComparable(id));
    }

    /**
     * Зпросить список всех добавленных адресов
     * @return массив адресов
     */
    public NetworkAddress [] getServers(){
        return (NetworkAddress[]) serversTable.values().toArray();
    }

    /**
     * Добавить задачу в планировщик
     * @param task задача, не null !
     * @return true - задача добавлена, false - задача с указанным id уже есть
     */
    public boolean addTask(JTask task){
        if (task==null) throw new NullPointerException();
        long servID;
        boolean res=false;
        NetworkAddress address=null;
        ISlaveServer server=null;
        //проверить наличие задачи в системе
        LongComparable tempID=new LongComparable(task.getTaskID());
        if(taskTable.containsKey(tempID)) return false;
        //передать задачу на планировщик нижнего уровня
        do{
            //определить свободный сервер по степени наполнения задачами
            try{servID=getMostFreeServer();}
            catch(Exception e){ //невозможность выбора сервера
                //добавить задачу к нераспрееленным
                strayedTasks.push(task);
                return true;
            } 
            //направить задачу на локальный планировщик нижнего уровня
            if(servID==s2slogic.getGUID()){
                res=localTM.addTask(task);
                if(!res){ //задача с таким id уже есть
                    System.out.println("При добавлении задачи ID="+tempID.value+
                            " На локальный планировщик нижнего уровня обнаружено"+
                            " нарушение целостности системы");
                    return false;
                }
            }
            //направить задачу на удаленный планировщик нижнего уровня
            else{
                address=serversTable.get(new LongComparable(servID));
                try {
                    //получить заглушку
                    server = s2slogic.getIArbitrator().getSlaveStub(address);
                    if(server==null){
                        System.out.println("При получении заглушки сервера ID="+
                                address.getGUID()+" обнаружено нарушение целостности системы");
                        return false;
                    }
                    //добавить задачу
                    res=server.addTask(task);
                    if(!res){ //задача с таким id уже есть
                        System.out.println("При добавлении задачи ID="+tempID.value+
                                " на удаленный планировщик нижнего уровня обнаружено"+
                                " нарушение целостности системы");
                        return false;
                    }
                } catch (RemoteException ex) {} //ошибка соединения
            }
        }while(!res);
        //добавить задачу в таблицу id задачи->JTask
        taskTable.put(tempID, task);
        //добавить задачу в таблицу id задачи->id сервера
        taskServersTable.put(tempID, new LongComparable(servID));
        return true;
    }
    
    /**
     * Удалить задачу из планировщика
     * Процессы связанные с задачей уничтожаются
     * @param taskID идентификатор задачи
     * @return true - задача удалена, false - задачи нет или ошибка сети
     */
    public boolean removeTask(long taskID){
        //проверить наличие задачи в системе
        LongComparable tempID=new LongComparable(taskID);
        LongComparable servID=taskServersTable.get(tempID);
        if(servID==null) return false;
        //получить удаленный объект
        NetworkAddress address=serversTable.get(servID);
        try {
            ISlaveServer server = s2slogic.getIArbitrator().getSlaveStub(address);
            //удалить задачу с удаленного планировщика
            if(!server.removeTask(taskID)){
                System.out.println("При удалении задачи ID="+taskID+
                        " с удаленного планировщика нижнего уровня обнаружено"+
                        " нарушение целостности системы");
                return false;
            }
            //удалить задачу из хэш таблиц
            taskTable.remove(tempID);
            taskServersTable.remove(tempID);
            return true;
        } catch (RemoteException ex) {
            return false;
        }
    }

    /**
     * Остановить выполнение задачи
     * @param taskID идентификатор задачи
     * @return true - задача остановлена, false - задачи нет
     */
    public boolean stopTask(long taskID){
        //проверить наличие задачи в системе
        LongComparable tempID=new LongComparable(taskID);
        LongComparable servID=taskServersTable.get(tempID);
        if(servID==null) return false;
        //получить удаленный объект
        NetworkAddress address=serversTable.get(servID);
        try {
            ISlaveServer server = s2slogic.getIArbitrator().getSlaveStub(address);
            //остановить задачу на удаленном планировщике
            if(!server.stopTask(taskID)){
                System.out.println("При остановке задачи ID="+taskID+
                        " на удаленном планировщике нижнего уровня обнаружено"+
                        " нарушение целостности системы");
                return false;
            }
            return true;
        } catch (RemoteException ex) {
            return false;
        }
    }

    /**
     * Проверить наличие задачи
     * @param taskID идентификатор задачи
     * @return true - задача присутствует, false иначе
     */
    public boolean containsTask(long taskID){
        return taskTable.containsKey(new LongComparable(taskID));
    }

    /**
     * Запросить задачу
     * @param taskID идентификатор задачи
     * @return задача или null в случае отсутствия
     */
    public JTask getTask(long taskID){
        JTask res=null;
        //проверить наличие задачи в системе
        LongComparable tempID=new LongComparable(taskID);
        //задачи нет
        if(!taskTable.containsKey(tempID)) return res;
        //получить сервер на котором выполняется задача
        LongComparable servID=taskServersTable.get(tempID);
        if(servID==null){ //нет привязки к серверу
            res=new JTask(taskTable.get(tempID));
            res.setStatus(JTask.STRAYED);
            return res;
        }
        //запросить удаленный объект
        NetworkAddress address=null;
        ISlaveServer server=null;
        address=serversTable.get(servID);
        try {
            //получить заглушку
            server = s2slogic.getIArbitrator().getSlaveStub(address);
            if(server==null){ //заглушка не задана
                System.out.println("При получении заглушки сервера ID="+
                        address.getGUID()+" обнаружено нарушение целостности системы");
                res=new JTask(taskTable.get(tempID));
                res.setStatus(JTask.UNKNOWN);
                return res;
            }
            //запросить задачу у удаленного сервера
            res=server.getTask(taskID);
        } catch (RemoteException ex) { //ошибка сети
            res=new JTask(taskTable.get(tempID)); //создать копию исходной задачи
            res.setStatus(JTask.UNKNOWN); //установить признак неопределенности
        }
        return res;
    }

    /**
     * Запросить идентификаторы задач в планировщике
     * @return массив идентификаторов задач
     */
    public long[] getAllTasks(){
        int i=0;
        long res[]=new long [taskTable.size()];
        Iterator<LongComparable> iter=taskTable.keySet().iterator();
        //перебор таблицы задач и заполнения массива идентификаторов задач
        while(iter.hasNext()){
            res[i]=iter.next().value;
            i++;
        }
        return res;
    }

    /**
     * Запросить идентификаторы задач в планировщике заданного сервера
     * @param serverID инентификатор сервера
     * @return массив идентификаторов задач
     */
    public long[] getAllTasks(long serverID){
        //проверить наличие сервера в системе
        LongComparable tempID=new LongComparable(serverID);
        NetworkAddress address=serversTable.get(tempID);
        ISlaveServer server=null;
        if(address==null) return null;
        //получить удаленную заглушку
        try{
            server = s2slogic.getIArbitrator().getSlaveStub(address);
            if(server==null){ //заглушка не задана
                System.out.println("При получении заглушки сервера ID="+
                        address.getGUID()+" обнаружено нарушение целостности системы");
                return null;
            }
            return server.getAllTasks();
        }
        catch (RemoteException ex) { //ошибка сети
            return null;
        }
    }

    /**
     * Запросить идентификаторы ожидающих и приостановленных задач заданного сервера
     * @param serverID инентификатор сервера
     * @return массив идентификаторов задач
     */
    public long[] getWaitTasks(long serverID){
        //проверить наличие сервера в системе
        LongComparable tempID=new LongComparable(serverID);
        NetworkAddress address=serversTable.get(tempID);
        ISlaveServer server=null;
        if(address==null) return null;
        //получить удаленную заглушку
        try{
            server = s2slogic.getIArbitrator().getSlaveStub(address);
            if(server==null){ //заглушка не задана
                System.out.println("При получении заглушки сервера ID="+
                        address.getGUID()+" обнаружено нарушение целостности системы");
                return null;
            }
            return server.getWaitTasks();
        }
        catch (RemoteException ex) { //ошибка сети
            return null;
        }
    }

    /**
     * Запросить идентификаторы активных задач заданного сервера
     * @param serverID инентификатор сервера
     * @return массив идентификаторов задач
     */
    public long[] getActiveTasks(long serverID){
        //проверить наличие сервера в системе
        LongComparable tempID=new LongComparable(serverID);
        NetworkAddress address=serversTable.get(tempID);
        ISlaveServer server=null;
        if(address==null) return null;
        //получить удаленную заглушку
        try{
            server = s2slogic.getIArbitrator().getSlaveStub(address);
            if(server==null){ //заглушка не задана
                System.out.println("При получении заглушки сервера ID="+
                        address.getGUID()+" обнаружено нарушение целостности системы");
                return null;
            }
            return server.getActiveTasks();
        }
        catch (RemoteException ex) { //ошибка сети
            return null;
        }
    }

    /**
     * Событие завершения задачи
     * удалить задачу из системы и попытаться активировать следующую задачу
     * освободившегося сервера
     * @param e событие
     */
    void endingTask(MpiEvent e) {
        //
        // ДОПИСАТЬ
        //
    }

    /**
     * Ошибка запуска задачи
     * перенести задачу в список ожидающих и попытаться активировать следующую задачу
     * освободившегося сервера
     * @param e событие
     */
    void errorTask(MpiEvent e) {
        //
        // ДОПИСАТЬ
        //
    }
    
}
